package engine;

/**
 * Statemachine based on {@link StatemachineModel}.
 *
 * <p>When using this statemachine across several objects, don't forget to make
 * your implementation singleton for memory reuse.</p>
 *
 * @author Ricardo Cruz {@literal <ricardo.pdm.cruz@gmail.com>}
 * @since  2014-11-12
 */
public class Statemachine implements SchedulerListener
{
	@SuppressWarnings("rawtypes")
	private StatemachineModel model;
	protected State state;

	/**
	 * Create a new statemachine based on a given statemachine model.
	 *
	 * @param your skeleton
	 */
	@SuppressWarnings("rawtypes")
	protected Statemachine(StatemachineModel model, State initial) {
		this.model = model;
		state = initial;
	}

	/**
	 * Stimulate a transition action.
	 * @param event
	 * @param eventData some data you will be given back on the transition function
	 */
	public void issue(StateEvent event, Object eventData) {
		event(new IssueEvent(event, eventData));
	}

	/**
	 * Within your transition actions, you may want to further issue events.
	 * @param time mean time to elapse
	 * @param event
	 * @param eventData some data you will be given back on the transition function
	 */
	public void issue(double time, StateEvent event, Object eventData) {
		Scheduler.post(time, this, new IssueEvent(event, eventData));
	}

	/**
	 * Within your transition actions, you may want to further issue events.
	 * @param time mean time to elapse
	 * @param time_dev time deviation, assuming a Normal
	 * @param event
	 * @param eventData some data you will be given back on the transition function
	 */
	public void issue(double time, double time_dev, StateEvent event, Object eventData) {
		Scheduler.post(time, time_dev, this, new IssueEvent(event, eventData));
	}

	/**
	 * For introspection purposes, returns current active state.
	 * @return state active state
	 */
	public State getCurrentState() {
		return state;
	}

	private class IssueEvent {
		public StateEvent event;
		public Object eventData;
		public IssueEvent(StateEvent event, Object eventData) {
			this.event = event;
			this.eventData = eventData;
		}
	}

	/**
	 * Internal usage.
	 * @param event object of type IssueEvent
	 */
	public void event(Object event) {
		if(state == null) return;  // death
		IssueEvent ev = (IssueEvent)event;
		State oldstate = state;
		if(model.trigger(this, state, ev.event, ev.eventData))
			Simulation.get().getAgentsLayer().cellStateChanged(oldstate, state);
		else if(model.trigger(this, null, ev.event, ev.eventData))
			Simulation.get().getAgentsLayer().cellStateChanged(oldstate, state);
	}
}
